import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.Queue;

/**
 * Iscas 85 Simulator
 * This is the main class. Execute it to execute the application
 *
 */
public class Iscas85Simulator {

	private GraphBuilder graphBuilder = null;
	private double error = 0.0;
	private byte fill = -1;
	private static final String fill0 = "f0";
	private static final String fill1 = "f1";
	
	/**
	 * Constructor for the Iscas85Simulator class
	 * @param graphBuilder
	 * 			GraphBuilder object with the circuit
	 */
	public Iscas85Simulator(GraphBuilder graphBuilder) {
		this.graphBuilder = graphBuilder;
	}
	
	/**
	 * Loads the input values from an input file
	 * @param filename
	 * 				Path to the input file
	 * @return Returns false on error (returns true otherwise)
	 */
	public boolean loadInputValues(String filename) {
		try {
			BufferedReader input = new BufferedReader(new InputStreamReader(new FileInputStream(filename)));
			String line = null;
			while(true) {
				line = input.readLine();
				if(line == null) {
					System.out.println("Error: could not parse circuit input file");
					return false;
				}
				line = line.trim();
				if(line.equals("") || line.charAt(0) == '#')
					continue;
				else
					break;
			}
			
			// Parse error and fill option
			String[] options = line.split(":");
			error = Double.parseDouble(options[0]);
			if(options[1].toLowerCase().equals(fill0)) {
				fill = 0;
			} else if(options[1].toLowerCase().equals(fill1)) {
				fill = 1;
			} else {
				System.out.println("Error: incorrect fill option in input file");
				return false;
			}
			if(error < 0.0 || error > 1.0) {
				System.out.println("Error: error probability in input file must be between 0 and 1");
				return false;
			}
			
			// Give values to input nodes
			while((line = input.readLine()) != null) {
				line = line.trim();
				if(line.equals("") || line.charAt(0) == '#')
					continue;
				String[] values = line.split(":");
				if(values[0].equals("*")) {
					// Anonymous node
					for(Iscas85Node node : graphBuilder.inputNodes) {
						if(node.value == -1) {
							node.value = Byte.parseByte(values[1]);
							if(node.value != 0 && node.value != 1) {
								System.out.println("Error: input values must be 0 or 1");
								return false;
							}
							break;
						}
					}
				} else {
					// Explicit indexed node
					SimpleNode node = graphBuilder.nameHash.get(values[0].trim().toUpperCase());
					if(node == null) {
						System.out.println("Error: input file references a nonexistant node");
						return false;
					}
					else {
						if(node.i85Gate != NetlistParser.GateType.INPT) {
							System.out.println("Error: reference to non input node in input file");
							return false;
						}
						node.i85GraphNode.value = Byte.parseByte(values[1]);
						if(node.i85GraphNode.value != 0 && node.i85GraphNode.value != 1) {
							System.out.println("Error: input values must be 0 or 1");
							return false;
						}
					}
				}
			}
			
			// Fill any blank nodes
			for(Iscas85Node node : graphBuilder.inputNodes) {
				if(node.value == -1)
					node.value = fill;
			}
			
		} catch (FileNotFoundException e) {
			System.out.println("Error: circuit input file not found");
			return false;
		} catch (IOException e) {
			System.out.println("Error: could not parse circuit input file");
			return false;
		} catch (NumberFormatException e) {
			System.out.println("Error: invalid value for numeric type in input file");
			return false;
		} catch (ArrayIndexOutOfBoundsException e) {
			System.out.println("Error: values missing in input file");
			return false;
		}
		
		return true;
	}
	/**
	 * Runs the simulator
	 */
	public void simulate() {
		Queue<Iscas85Node> queue = new LinkedList<Iscas85Node>();
		queue.addAll(graphBuilder.inputNodes);
		for(Iscas85Node node : graphBuilder.inputNodes)
			node.active = true;
		
		while(queue.size() > 0) {
			Iscas85Node node = queue.remove();
			node.active = false;
			if(node instanceof Iscas85FixValue) {
				// Append node to the end of the list
				if(node.value == -1) {
					node.active = true;
					queue.add(node);
					continue;
				}
				else {
					node.simulateError(this.error);
					node.processInput();
					// Iterate through the node's children
					for(Iscas85Edge edge : graphBuilder.graph.outgoingEdgesOf(node)) {
						Iscas85Node childNode = graphBuilder.graph.getEdgeTarget(edge);
						if(childNode instanceof Iscas85FixValue)
							childNode.value = node.value;
						else
							((Iscas85Logic)childNode).insertValue(node.value);
						if(!childNode.active)
						{
							childNode.active = true;
							queue.add(childNode);
						}
					}
				}
			} else {
				Iscas85Logic logicNode = ((Iscas85Logic)node);
				if(logicNode.isComplete()) {
					logicNode.simulateError(this.error);
					logicNode.processInput();
					for(Iscas85Edge edge : graphBuilder.graph.outgoingEdgesOf(node)) {
						Iscas85Node childNode = graphBuilder.graph.getEdgeTarget(edge);
						if(childNode instanceof Iscas85FixValue)
							childNode.value = logicNode.value;
						else
							((Iscas85Logic)childNode).insertValue(logicNode.value);
						if(!childNode.active)
						{
							childNode.active = true;
							queue.add(childNode);
						}
					}
				} else {
					logicNode.active = true;
					queue.add(logicNode);
				}
			}
		}
	}
	/**
	 * Outupts the simulation result to a file
	 * @param filename
	 * 			Name of the output file
	 * @param inputFile
	 * 			Name of the netlist file (only used to print the name)
	 */
	public void outputResults(String filename, String inputFile) {
		FileWriter outFile;
		try {
			outFile = new FileWriter(filename);
			PrintWriter out = new PrintWriter(outFile);
			out.println("# Output result for the circuit defined in");
			out.println("# " + inputFile + "\n");
			for(Iscas85Node node : graphBuilder.outputNodes) {
				out.println(node.name + ":" + node.value);
			}
			out.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	/**
	 * Prints the simulation results 
	 */
	public void printResults() {
		for(Iscas85Node node : graphBuilder.outputNodes) {
			System.out.println(node.name + ":" + node.value);
		}
	}
}
